﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Forms;
using BGShell.Collections;
using BGShell.Interop;
using WindowsFormsAero;

namespace BGShell.Components
{
    [System.ComponentModel.DesignerCategory("Code")]
    public sealed class KeyboardHook : EnableableComponent
    {
        private KeyboardHookInfoFile _infoFile = new KeyboardHookInfoFile(); 
        private SharedMemorySection _section;
        private HookNativeWindow _window;
        private WindowsHook _hook;

        public KeyboardHook()
            : this(null)
        {
        }

        public KeyboardHook(IContainer container)
        {
            if (container != null)
            {
                container.Add(this);
            }

            _window = new HookNativeWindow(this);
        }

        public event KeyEventHandler KeyDown
        {
            add { _window.KeyDown += value; }
            remove
            {
                if (_window != null)
                {
                    _window.KeyDown -= value; 
                }
            }
        }

        public HashSet<Keys> ShortcutKeys
        {
            get { return _infoFile.ShortcutKeys; }
        }

        public Collection<String> Strings
        {
            get { return _infoFile.Strings; }
        }

        public void UpdateHook()
        {
            if (Enabled)
            {
                _infoFile.Save();
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_window != null)
                {
                    _window.DestroyHandle();
                    _window = null;
                }
            }

            base.Dispose(disposing);
        }

        protected override void OnStart()
        {
            bool createdNew;

            _section = new SharedMemorySection(false, "BGShell-KeyboardHook-Section", IntPtr.Size, out createdNew);

            if (!createdNew)
            {
                _section.Dispose();
                _section = null;

                throw new InvalidOperationException(Resources.BackgroundBGShellIsAlreadyRunning);
            }

            _section.WriteIntPtr(0, _window.Handle);
            _infoFile.Handle = _window.Handle;
            _infoFile.Save();

            _hook = new WindowsHook()
            {
                HookType = WindowsHookType.Keyboard,
                ModuleName = "BGShellHook32.dll",
                FunctionName = "KeyboardProc",
                Enabled = true,
            };
        }

        protected override void OnStop()
        {
            if (_infoFile != null)
            {
                _infoFile = null;
            }

            if (_hook != null)
            {
                _hook.Dispose();
                _hook = null;
            }

            if (_section != null)
            {
                _section.Dispose();
                _section = null;
            }
        }

        private sealed class HookNativeWindow : NativeWindow
        {
            private static readonly Int32 HookMessage = NativeMethods.RegisterWindowMessageW("WM_BGShell_KeyboardHook");
            private static readonly IntPtr WParamKeyDown = IntPtr.Zero;

            private readonly object _sender;

            private KeyEventHandler _keyDownEvent;

            public event KeyEventHandler KeyDown
            {
                add { _keyDownEvent += value; }
                remove { _keyDownEvent -= value; }
            }

            public HookNativeWindow(object sender)
            {
                _sender = sender;

                CreateHandle(new CreateParams()
                {
                    Parent = new IntPtr(-3), // HWND_MESSAGE
                });
            }

            public override void DestroyHandle()
            {
                _keyDownEvent = null;
                base.DestroyHandle();
            }

            protected override void WndProc(ref Message m)
            {
                if (m.Msg == HookMessage)
                {
                    WmHookMessage(ref m);
                }
                else
                {
                    base.WndProc(ref m);
                }
            }

            private void WmHookMessage(ref Message m)
            {
                if (m.WParam == WParamKeyDown)
                {
                    var keyData = (Keys)(m.LParam.ToInt32());
                    var handler = _keyDownEvent;

                    if (handler != null)
                    {
                        handler(_sender, new KeyEventArgs(keyData));
                    }
                }
            }
        }
    }
}